﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////

#pragma once

#include "GnufBase.h"

namespace {
    class HooksInsideOfObjectParasiteEx;
}

#ifndef _GNUF_IMPLEMENT_OBJECTPARASITEEX_USING_HEAP_MEMORY_
#define _GNUF_IMPLEMENT_OBJECTPARASITEEX_USING_HEAP_MEMORY_
#endif // _GNUF_IMPLEMENT_OBJECTPARASITEEX_USING_HEAP_MEMORY

namespace gnuf {

    class ObjectParasite;
    class ObjectParasiteEx;

    class GNUF_BASE_EXPORT ObjectHookBase
    {
    protected:
        explicit ObjectHookBase();
        // Don't expect the user to delete the object whose class inherits from
        // ObjectHookBase, through the pointer to ObjectHookBase, so don't
        // declare it as virtual. If did it, the memory size would increase 8 
        // bytes for VPTR.
        ~ObjectHookBase();
        
    private:
        // Do not allow to copy and assign.
        ObjectHookBase(const ObjectHookBase &);
        ObjectHookBase & operator=(const ObjectHookBase &);
        
    public:
        /// \return Return true if hooked. Return false, otherwise.
        bool isHooked() const { return (nullptr != m_pObject); }
        
        /// \brief Unhook from the parasite.
        /// \param pObject: the pointer to the object.
        /// \param parasite: the parasite inside of the object.
        void unhookFromParasite(const void * pObject, ObjectParasite & parasite);
        
        /// \brief Unhook from the parasite.
        /// \param pObject: the pointer to the object.
        /// \param parasiteEx: the parasite inside of the object.
        void unhookFromParasiteEx(const void * pObject, ObjectParasiteEx & parasiteEx);
            
    protected:
        /// \return Return the const pointer to the object.
        const void * _object() const { return m_pObject; }
        /// \return Return the pointer to the object.
        void * _object() { return m_pObject; }
        
        /// \brief Hook the object.
        template <class TObject>
        void hook(TObject * pObject)
        {
            _hook(pObject, pObject->m_parasite);
        }
        
        /// \brief Unhook from the hook. Support unhooking from the object
        /// and the hook. See also, ObjectHookBase::unhookFromObject().
        template <class TObject>
        void unhookFromHook(TObject * pObject)
        {
            _unhook(pObject, pObject->m_parasite);
        }
        
    private:
        /// \brief Hook the object.
        /// \param pObject: the pointer to the object.
        /// \param parasite: the parasite inside of the object.
        void _hook(void * pObject, ObjectParasite & parasite);
        /// \brief Hook the object.
        /// \param pObject: the pointer to the object.
        /// \param parasite: the parasite inside of the object.
        void _hook(void * pObject, ObjectParasiteEx & parasiteEx);
        /// \brief Unhook from the object.
        /// \param pObject: the pointer to the object.
        /// \param parasite: the parasite inside of the object.
        void _unhook(const void * pObject, ObjectParasite & parasite);
        /// \brief Unhook from the object.
        /// \param pObject: the pointer to the object.
        /// \param parasite: the parasite inside of the object.
        void _unhook(const void * pObject, ObjectParasiteEx & parasiteEx);
        
    private:
        // Do not know the type, so have to cache it using a void pointer.
        void * m_pObject;
    };

    // 1..1
    class GNUF_BASE_EXPORT ObjectParasite final
    {
    public:
        explicit ObjectParasite();
        ~ObjectParasite();
        
    private:
        // Do not allow to copy and assign.
        ObjectParasite(const ObjectParasite &);
        ObjectParasite & operator=(const ObjectParasite &);
        
    public:
        /// \return Return true if hooked. Return false, otherwise.
        bool isHooked() const { return (nullptr != m_pHook); }
        /// \brief Unhook from the object.
        /// \param pObject: the point to the object.
        void unhook(const void * pObject);
        
    private:
        friend class ObjectHookBase;
        ObjectHookBase * m_pHook;
    };
    
    // 1..N
    class GNUF_BASE_EXPORT ObjectParasiteEx final
    {
    public:
        explicit ObjectParasiteEx();
        ~ObjectParasiteEx();
        
    private:
        // Do not allow to copy and assign.
        ObjectParasiteEx(const ObjectParasiteEx &);
        ObjectParasiteEx & operator=(const ObjectParasiteEx &);
        
    public:
        /// \return Return true if hooked. Return false, otherwise.
        bool isHooked(size_t * pSize = nullptr) const;
        /// \brief Unhook from the object.
        /// \param pObject: the point to the object.
        void unhook(const void * pObject);
        
    private:
        friend class ObjectHookBase;
#ifdef _GNUF_IMPLEMENT_OBJECTPARASITEEX_USING_HEAP_MEMORY_
        gcmp::OwnerPtr< HooksInsideOfObjectParasiteEx > m_opHooks;
#else
        std::unordered_set< ObjectHookBase * > m_hooks;
#endif // _GNUF_IMPLEMENT_OBJECTPARASITEEX_USING_HEAP_MEMORY_
    };

    template <class TObject>
    class ObjectHook final : public ObjectHookBase
    {
    public:
        explicit ObjectHook() {}
        ObjectHook(TObject *);
        ObjectHook(ObjectHook< TObject > &);
        
    private:
        // Prevent from newing and deleting in the heap memory.
        void * operator new     (size_t);
        void   operator delete  (void *);
    
    public:
        // ObjectHookBase's destructor isn't virtual, so don't have to
        // declare ObjectHook's destructor as virtual. See also, 
        // ObjectHookBase::~ObjectHookBase().
        ~ObjectHook();
        /// \brief Assign an object.
        ObjectHook & operator=(TObject * pObject);
        /// \brief Assign an object.
        ObjectHook & operator=(ObjectHook< TObject > &);
        /// \return Return true if valid. Return false, otherwise.
        bool isValid() { return isHooked(); }
        /// \return Return the pointer to the object.
        TObject * operator->() { return get(); }
        /// \return Return the const pointer to the object.
        const TObject * operator->() const { return get(); }
        /// \return Return the pointer to the object.
        TObject * get();
        /// \return Return the const pointer to the object.
        const TObject * get() const;
        /// \return Return true if hooked. Return false if not hooked.
        operator bool() const { return isHooked(); }
        /// \return Return true if not hooked. Return false if hooked.
        bool operator!() const { return !isHooked(); }
        /// \return Return the object reference.
        TObject & operator*();
        /// \return Return the const object reference.
        const TObject & operator*() const;
        /// \brief Set the object.
        /// \param pObject: the pointer to the object.
        /// If nullptr, means to call unset().
        void reset(TObject * pObject = nullptr);
        
    private:
        void suckFrom(ObjectHook< TObject > &);
    };
}

#define GNUF_OBJECT_PARASITE_UNHOOK(ClassName) \
    unhook##ClassName();

#define GNUF_OBJECT_PARASITE(ClassName) \
    public: \
        bool is##ClassName##Hooked() const \
        { \
            return this->m_parasite.isHooked(); \
        } \
    protected: \
        void unhook##ClassName() \
        { \
            this->m_parasite.unhook(this); \
            DBG_WARN_UNLESS(!is##ClassName##Hooked(), \
                L"Not unhooked!", L"fangkunfk", L"2022-05-24"); \
        } \
    private: \
        friend class gnuf::ObjectHookBase; \
        gnuf::ObjectParasite m_parasite;
        
#define GNUF_OBJECT_PARASITE_EX(ClassName) \
    public: \
        bool is##ClassName##Hooked(size_t * pSize = nullptr) const \
        { \
            return this->m_parasite.isHooked(pSize); \
        } \
    protected: \
        void unhook##ClassName() \
        { \
            this->m_parasite.unhook(this); \
            DBG_WARN_UNLESS(!is##ClassName##Hooked(), \
                L"Not unhooked!", L"fangkunfk", L"2022-05-24"); \
        } \
    private: \
        friend class gnuf::ObjectHookBase; \
        gnuf::ObjectParasiteEx m_parasite;

#define GNUF_OBJECT_PARASITE_INTERFACE(ClassName) \
    protected: \
        ClassName() {} \
        ~##ClassName() { unhook##ClassName(); } \
        GNUF_OBJECT_PARASITE(ClassName)

#define GNUF_OBJECT_PARASITE_INTERFACE_EX(ClassName) \
    protected: \
        ClassName() {} \
        ~##ClassName() { unhook##ClassName(); } \
        GNUF_OBJECT_PARASITE_EX(ClassName)

//_____________________________________________________________________________
// ObjectHook template class implementation
//
template <class TObject>
gnuf::ObjectHook<TObject>::ObjectHook(TObject * pObject)
{
    reset(pObject);
}

template <class TObject>
gnuf::ObjectHook<TObject>::ObjectHook(gnuf::ObjectHook<TObject> & that)
{
    suckFrom(that);
}

template <class TObject>
gnuf::ObjectHook<TObject>::~ObjectHook()
{
    reset(nullptr);
}

template <class TObject>
gnuf::ObjectHook<TObject> &
gnuf::ObjectHook<TObject>::operator=(TObject * pObject)
{
    reset(pObject);
    return *this;
}

template <class TObject>
gnuf::ObjectHook<TObject> &
gnuf::ObjectHook<TObject>::operator=(gnuf::ObjectHook<TObject> & that)
{
    suckFrom(that);
    return *this;
}

template <class TObject>
TObject *
gnuf::ObjectHook<TObject>::get()
{
    if (!isHooked())
        return nullptr;
    
    return reinterpret_cast<TObject *>(_object());
}

template <class TObject>
const TObject *
gnuf::ObjectHook<TObject>::get() const
{
    if (!isHooked())
        return nullptr;
    
    return reinterpret_cast<const TObject *>(_object());
}

template <class TObject>
TObject &
gnuf::ObjectHook<TObject>::operator*()
{
    DBG_WARN_UNLESS(isHooked(), L"Not Hooked!", L"GMEP", L"2022-05-27");
    return *get();
}

template <class TObject>
const TObject &
gnuf::ObjectHook<TObject>::operator*() const
{
    DBG_WARN_UNLESS(isHooked(), L"Not Hooked!", L"GMEP", L"2022-05-27");
    return *get();
}

template <class TObject>
void gnuf::ObjectHook<TObject>::reset(TObject * pObject)
{
    // Unhook, first.
    if (isHooked())
    {
        TObject * pObject = get();
        unhookFromHook(pObject);
    }

    if (nullptr == pObject)
        return;

    // And then, hook.
    hook(pObject);
}

template <class TObject>
void gnuf::ObjectHook<TObject>::suckFrom(gnuf::ObjectHook<TObject> & that)
{
    if (!that.isHooked())
        return;
    
    TObject * pObject = that.get();
        
    // Unhook that.
    that.reset();
    
    // Hook this.
    reset(pObject);
}